package main

import (
	"fmt"
	"unsafe"
)

/*
	指针是一个指向某个确切的内存地址的值。

	在 Go 语言中还有其他几样东西可以代表“指针”。其中最贴近传统意义的当属uintptr类型了。该类型实际上是一个数值类型，也是 Go 语言内建的数据类型之一。

	Go 语言标准库中的unsafe包。unsafe包中有一个类型叫做Pointer，也代表了“指针”。
	unsafe.Pointer可以表示任何指向可寻址的值的指针，同时它也指针值和uintptr值之间的桥梁。
	也就是说，通过它，我们可以在这两种值之上进行双向的转换。这里有一个很关键的词——可寻址的（addressable）。

能列举出 Go 语言中的哪些值是不可寻址的吗？
	常量的值。
	基本类型值的字面量。
	算术操作的结果值。
	对各种字面量的索引表达式和切片表达式的结果值。不过有一个例外，对切片字面量的索引结果值却是可寻址的。
	对字符串变量的索引表达式和切片表达式的结果值。
	对字典变量的索引表达式的结果值。
	函数字面量和方法字面量，以及对它们的调用表达式的结果值。
	结构体字面量的字段值，也就是对结构体字面量的选择表达式的结果值。
	类型转换表达式的结果值。
	类型断言表达式的结果值。
	接收表达式的结果值。


	1.不可变的值不可寻址。常量、基本类型的值字面量、字符串变量的值、函数以及方法的字面量都是如此。其实这样规定也有安全性方面的考虑。
	2.绝大多数被视为临时结果的值都是不可寻址的。算术操作的结果值属于临时结果，针对值字面量的表达式结果值也属于临时结果。
		但有一个例外，对切片字面量的索引结果值虽然也属于临时结果，但却是可寻址的。
	3.若拿到某值的指针可能会破坏程序的一致性，那么就是不安全的，该值就不可寻址。
		由于字典的内部机制，对字典的索引结果值的取址操作都是不安全的。另外，获取由字面量或标识符代表的函数或方法的地址显然也是不安全的。

问题 1：不可寻址的值在使用上有哪些限制？
	首当其冲的当然是无法使用取址操作符&获取它们的指针了。

问题 2：怎样通过unsafe.Pointer操纵可寻址的值？
	unsafe.Pointer是像*Dog类型的值这样的指针值和uintptr值之间的桥梁，
	可以让任意类型的指针实现相互转换，也可以将任意类型的指针转换为 uintptr 进行指针运算。unsafe.Pointer 不能参与指针运算
	那么怎样利用unsafe.Pointer的中转和uintptr的底层操作来操纵像dog这样的值呢。

	1.一个指针值（比如*Dog类型的值）可以被转换为一个unsafe.Pointer类型的值，反之亦然。
	2.一个uintptr类型的值也可以被转换为一个unsafe.Pointer类型的值，反之亦然。
	3.一个指针值无法被直接转换成一个uintptr类型的值，反过来也是如此。

	对于指针值和uintptr类型值之间的转换，必须使用unsafe.Pointer类型的值作为中转。

	把指针值转换成uintptr类型的值有什么意义吗?

	需要与unsafe.Offsetof函数搭配使用才能看出端倪。unsafe.Offsetof函数用于获取两个值在内存中的起始存储地址之间的偏移量，以字节为单位。
	例如:
		namePtr := dogPtr + unsafe.Offsetof(dogP.name)
		nameP := (*string) (unsafe.Pointer(namePtr))


 */

type Dog struct {
	name string
}

/*
	对于基本类型Dog来说，*Dog就是它的指针类型。
	而对于一个Dog类型，值不为nil的变量dog，取址表达式&dog的结果就是该变量的值（也就是基本值）的指针值。
 */
func (dog *Dog) SetName(name string)  {
	dog.name = name
}

/*
	这个函数会接受一个名为name的string类型的参数，并会用这个参数初始化一个Dog类型的值，最后返回该值
 */
func New(name string) Dog {
	return Dog{name}
}

func main() {

	/*
		对于一个Dog类型的变量dog来说，调用表达式dog.SetName("monster")会被自动地转译为(&dog).SetName("monster")，
		即：先取dog的指针值，再在该指针值上调用SetName方法。

		由于New函数的调用结果值是不可寻址的，所以无法对它进行取址操作。
	 */
	// New("little dog").SetName("monster")

	/*
		声明了一个Dog类型的变量dog，然后用取址操作符&，取出了它的指针值，并把它赋给了变量dogP。
	 */
	dog :=Dog{"little dog"}
	dogP := &dog
	/*
		使用了两个类型转换，先把dogP转换成了一个unsafe.Pointer类型的值，
		然后紧接着又把后者转换成了一个uintptr的值，并把它赋给了变量dogPtr。

	 */
	dogPtr :=uintptr(unsafe.Pointer(dogP))

	/*
		unsafe.Offsetof函数用于获取两个值在内存中的起始存储地址之间的偏移量，以字节为单位。
		有了这个偏移量，又有了结构体值在内存中的起始存储地址（这里由dogPtr变量代表），
		把它们相加就可以得到dogP的name字段值的起始存储地址了。这个地址由变量namePtr代表。
	 */
	namePtr := dogPtr + unsafe.Offsetof(dogP.name)

	/*
		再通过两次类型转换把namePtr的值转换成一个*string类型的值，这样就得到了指向dogP的name字段值的指针值。
	 */
	nameP :=(* string)(unsafe.Pointer(namePtr))

	*nameP = "large dog"

	fmt.Println("the dog is :", dog)

	
}
